// Copyright 2018-2019 Yubico AB
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import "YKFOTPTokenParser.h"
#import "YKFOTPTokenValidator.h"
#import "YKFOTPURIParser.h"
#import "YKFOTPTextParser.h"
#import "YKFLogger.h"
#import "YubiKitConfiguration.h"

@interface YKFOTPTokenParser()

@property (nonatomic, strong) id<YKFOTPTextParserProtocol> textParser;
@property (nonatomic, strong) id<YKFOTPURIParserProtocol> uriParser;

@end

@implementation YKFOTPTokenParser

- (instancetype)init {
    self = [super init];
    if (self) {
        YKFOTPTokenValidator *otpTokenValidator = [[YKFOTPTokenValidator alloc] init];
        
        if (YubiKitConfiguration.customOTPURIParser) {
            self.uriParser = YubiKitConfiguration.customOTPURIParser;
        } else {
            self.uriParser = [[YKFOTPURIParser alloc] initWithValidator:otpTokenValidator];
        }

        if (YubiKitConfiguration.customOTPTextParser) {
            self.textParser = YubiKitConfiguration.customOTPTextParser;
        } else {
            self.textParser = [[YKFOTPTextParser alloc] initWithValidator:otpTokenValidator];
        }
    }
    return self;
}

#pragma mark - YKFOTPTokenParserProtocol

- (id<YKFOTPTokenProtocol>)otpTokenFromNfcMessages:(NSArray *)messages {
    for (NFCNDEFMessage *message in messages) {
        id<YKFOTPTokenProtocol> token = [self extractTokenFromMessage:message];
        if (token) {
            return token; // Get the first one since the hardware key should generate only one OTP.
        }
    }
    return nil;
}

#pragma mark - Helpers

- (id<YKFOTPTokenProtocol>)extractTokenFromMessage:(NFCNDEFMessage *)message {
    for (NFCNDEFPayload *record in message.records) {
        // Filter to get only the TNF which is generated by the YubiKey
        if (record.typeNameFormat != NFCTypeNameFormatNFCWellKnown) {
            continue;
        }
        
        // Filter to get only URIs and text records as encoded by the YubiKey
        if (![self recordHasURI:record] && ![self recordHasText:record]) {
            continue;
        }
        
        NSString *payload = [self extractPayloadFromData:record.payload];
        if (!payload) {
            continue;
        }
        
        YKFOTPToken *token = [[YKFOTPToken alloc] init];
        
        if ([self recordHasURI:record]) {
            token.metadataType = YKFOTPMetadataTypeURI;
            YKFLogInfo(@"Metdata is URI");
            
            token.value = [self.uriParser tokenFromPayload:payload];
            YKFLogInfo(@"OTP value %@", token.value);
            
            token.uri = [self.uriParser uriFromPayload:payload];
            YKFLogInfo(@"OTP uri %@", token.uri);
        }
        else if ([self recordHasText:record]) {
            token.metadataType = YKFOTPMetadataTypeText;
            YKFLogInfo(@"Metdata is Text");
            
            token.value = [self.textParser tokenFromPayload:payload];
            YKFLogInfo(@"OTP value %@", token.value);
            
            token.text = [self.textParser textFromPayload:payload];
            YKFLogInfo(@"OTP text %@", token.text);
        }
     
        if (!token.value.length) { // Payload was extracted but it was not a valid or malformed URL/Text
            return nil;
        }
        
        id<YKFOTPTokenValidatorProtocol> validator = [[YKFOTPTokenValidator alloc] init];
        if ([validator maybeYubicoOTP:token.value]) {
            token.type = YKFOTPTokenTypeYubicoOTP;
        } else if ([validator maybeHOTP:token.value]) {
            token.type = YKFOTPTokenTypeHOTP;
        } else {
            token.type = YKFOTPTokenTypeUnknown;
        }
        
        return token;
    }
    return nil;
}

- (BOOL)recordHasURI:(NFCNDEFPayload *)record {
    if (!record.type.length) {
        return NO;
    }
    
    const UInt8* bytes = record.type.bytes;
    UInt8 recordType = bytes[0];
    
    return recordType == YKFNDEFWellKnownTypeURI;
}

- (BOOL)recordHasText:(NFCNDEFPayload *)record {
    if (!record.type.length) {
        return NO;
    }
    
    const UInt8* bytes = record.type.bytes;
    UInt8 recordType = bytes[0];
    
    return recordType == YKFNDEFWellKnownTypeText;
}

- (NSString *)extractPayloadFromData:(NSData *)data {
    if (!data.length) {
        return nil;
    }
    return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}

@end
